#include "CPLEXAdapter.h"

/*
 * The function copies the cluster editing problem from the graph into CPLEX.
 */
int GenerateCPLEXProblem(CPXENVptr* p_env, CPXLPptr* p_lp, graph* gr)
{
    int status;
    int numofConstraints;
    int numofVariables;
    int objectiveSense;
    /*Copy Problem Data Variables*/
    double* objective =NULL; /* obj*/
    char* constraintSenses =NULL; /* sense*/
    double* constraintsRHS =NULL; /* rhs*/
    double* variableCoeffs =NULL; /* matval*/
    int* variableCoeffsIndices=NULL; /* matbeg */
    int* constraintsPerVariable=NULL; /* matcnt*/
    int* variableCoeffsIndicesToConstraints=NULL; /* matint */
    double* variableUpperBounds=NULL; /* ub */
    double* variableLowerBounds=NULL; /* lb */
    int* variableIndices=NULL;
    char* variableCtypes=NULL;
    
    numofVariables = OVER_2(gr->numofVertices);
    numofConstraints = OVER_3(gr->numofVertices)*3;
    objectiveSense = CPX_MAX;
    
    /* Define Objective */
    objective = (double*)malloc(sizeof(double)*numofVariables);
    if(objective==NULL){
        fprintf (stderr, "Error: standard function malloc has failed\n");
        status = -1;
        goto TERMINATE;
    }
    populateObjective(gr, numofVariables, objective);
    
    /* Define Constraints */
    variableCoeffsIndices = malloc(sizeof(int)*numofVariables);
    constraintsPerVariable = malloc(sizeof(int)*numofVariables);
    constraintSenses = malloc(sizeof(char)*(numofConstraints+1));
    constraintsRHS = malloc(sizeof(double)*(numofConstraints));
    variableCoeffs = malloc(sizeof(double)*numofConstraints*3);
    variableCoeffsIndicesToConstraints = malloc(sizeof(int)*numofConstraints*3);
    variableLowerBounds = calloc(sizeof(double), numofVariables);
    variableUpperBounds = calloc(sizeof(double), numofVariables);
    if(variableCoeffsIndices == NULL || constraintsPerVariable == NULL || constraintSenses == NULL || constraintsRHS == NULL
       || variableCoeffs == NULL || variableCoeffsIndicesToConstraints == NULL || variableLowerBounds == NULL || variableUpperBounds == NULL){
        fprintf (stderr, "Error: standard function malloc has failed\n");
        status = -1;
        goto TERMINATE;
    }
    if(populateConstraints(numofVariables, numofConstraints,
                           constraintSenses, constraintsRHS,
                           variableCoeffsIndices, constraintsPerVariable,
                           variableCoeffs, variableCoeffsIndicesToConstraints,
                           variableLowerBounds, variableUpperBounds,gr) == ERROR){
        fprintf (stderr, "Error: Failed populating ILP constraints\n");
        status = -1;
        goto TERMINATE;
    }
    
    /*Copy Problem Data*/
    status = CPXcopylp(*p_env, *p_lp, numofVariables, numofConstraints, objectiveSense,	objective, constraintsRHS,	constraintSenses,
                       variableCoeffsIndices, constraintsPerVariable, variableCoeffsIndicesToConstraints, variableCoeffs,
                       variableLowerBounds, variableUpperBounds,
                       NULL);
    if (status) {
        fprintf (stderr, "Error: Failed to copy problem.\n");
        goto TERMINATE;
    }
    
    /*Change variables type to binary*/
    variableIndices = calloc(sizeof(int), numofVariables);
    variableCtypes = calloc(sizeof(char), numofVariables);
    if(variableIndices == NULL || variableCtypes == NULL){
        fprintf (stderr, "Error: standard function malloc has failed\n");
        status = -1;
        goto TERMINATE;
    }
    if(populateTypeDefinitionSettings(numofVariables, variableIndices, variableCtypes)==ERROR){
        fprintf (stderr, "Error: Failed populating ILP type definitions (Binary)\n");
        status = -1;
        goto TERMINATE;
    }
    CPXchgctype(*p_env, *p_lp, numofVariables, variableIndices, variableCtypes);
    if (status) {
        fprintf (stderr, "Error: Failed to change problem ctype.\n");
        goto TERMINATE;
    }
    
TERMINATE:
    free(objective);
    free(constraintSenses);
    free(constraintsRHS);
    free(variableCoeffs);
    free(variableCoeffsIndices);
    free(constraintsPerVariable);
    free(variableCoeffsIndicesToConstraints);
    free(variableUpperBounds);
    free(variableLowerBounds);
    free(variableIndices);
    free(variableCtypes);
    return status;
}

/*
 * Populates an objective array according to the graph
 */
int populateObjective(graph* gr, int numofVariables, double* objective)
{
	int i,j,k;
	double** adjMatrix;
	adjMatrix = getAdjMatrix(gr);
	k=0;
	for (i=0;i<gr->numofVertices;i++){
		for (j=i+1;j<gr->numofVertices;j++){
			if(k>numofVariables){
				/* unreachable code supposedly */
				return ERROR;
			}
			objective[k++] = adjMatrix[i][j];
		}
	}
    freeAdjMatrix(adjMatrix,gr -> numofVertices);
	return OK;
}

/*
 * Populates the senses and right hand side of the constraints.
 */
int populateSenseAndRHS(int numofConstraints, char* constraintSenses, double* constraintsRHS)
{
	int i;
	for (i=0;i<numofConstraints;i++){
		constraintSenses[i]='L';
		constraintsRHS[i]=1;
	}
	return OK;
}

/*
 * Populates all the constraints information
 */
int populateConstraints(int numofVariables, int numofConstraints, char* constraintSenses, double* constraintRHS, int* variableCoeffsIndexes, int* constraintsPerVariable, double* variableCoeffs, int* variableCoeffsIndexesToConstraints, double* variableLowerBounds, double* variableUpperBounds,graph* gr)
{
	int i, j, k, numofConsPerVar, constraintCounter, edge1,edge2,edge3;
	int* constraintCounterPerVariable;
	if (!populateSenseAndRHS(numofConstraints, constraintSenses, constraintRHS)){
		/* Unreachable code*/
		return ERROR;
	}
	numofConsPerVar = (gr ->numofVertices -2)*3;
	for(i=0;i<numofVariables;i++){
		constraintsPerVariable[i]=numofConsPerVar;
		variableCoeffsIndexes[i]=i*numofConsPerVar;
	}
    
	constraintCounterPerVariable = calloc(numofVariables, sizeof(int));
	if(constraintCounterPerVariable == NULL){
		fprintf (stderr, "Error: standard function malloc has failed\n");
		return ERROR;
	}
	constraintCounter=0;
	/*
	 * For each edge create a constraint by combining it with a vertex (which is not one of the edge' vertices
	 */
	for (i=0;i<gr -> numofVertices-2;i++){
		for (j=i+1;j<gr -> numofVertices-1;j++){
			for (k=j+1;k<gr -> numofVertices;k++){
                edge1= getVariableIndexFromVertices(gr, i,j);
                edge2= getVariableIndexFromVertices(gr, i,k);
                edge3= getVariableIndexFromVertices(gr, j,k);
                
				variableCoeffs[edge1*numofConsPerVar+constraintCounterPerVariable[edge1]]=1;
				variableCoeffsIndexesToConstraints[edge1*numofConsPerVar + constraintCounterPerVariable[edge1]++]=constraintCounter;
				variableCoeffs[edge2*numofConsPerVar+constraintCounterPerVariable[edge2]]=1;
				variableCoeffsIndexesToConstraints[edge2*numofConsPerVar + constraintCounterPerVariable[edge2]++]=constraintCounter;
				variableCoeffs[edge3*numofConsPerVar+constraintCounterPerVariable[edge3]]=-1;
				variableCoeffsIndexesToConstraints[edge3*numofConsPerVar + constraintCounterPerVariable[edge3]++]=constraintCounter;
				constraintCounter++;
				variableCoeffs[edge1*numofConsPerVar+constraintCounterPerVariable[edge1]]=1;
				variableCoeffsIndexesToConstraints[edge1*numofConsPerVar + constraintCounterPerVariable[edge1]++]=constraintCounter;
				variableCoeffs[edge2*numofConsPerVar+constraintCounterPerVariable[edge2]]=-1;
				variableCoeffsIndexesToConstraints[edge2*numofConsPerVar + constraintCounterPerVariable[edge2]++]=constraintCounter;
				variableCoeffs[edge3*numofConsPerVar+constraintCounterPerVariable[edge3]]=1;
				variableCoeffsIndexesToConstraints[edge3*numofConsPerVar + constraintCounterPerVariable[edge3]++]=constraintCounter;
				constraintCounter++;
				variableCoeffs[edge1*numofConsPerVar+constraintCounterPerVariable[edge1]]=-1;
				variableCoeffsIndexesToConstraints[edge1*numofConsPerVar + constraintCounterPerVariable[edge1]++]=constraintCounter;
				variableCoeffs[edge2*numofConsPerVar+constraintCounterPerVariable[edge2]]=1;
				variableCoeffsIndexesToConstraints[edge2*numofConsPerVar + constraintCounterPerVariable[edge2]++]=constraintCounter;
				variableCoeffs[edge3*numofConsPerVar+constraintCounterPerVariable[edge3]]=1;
				variableCoeffsIndexesToConstraints[edge3*numofConsPerVar + constraintCounterPerVariable[edge3]++]=constraintCounter;
				constraintCounter++;
			}
		}
	}
	free(constraintCounterPerVariable);
	if(populateVariableBounds(numofVariables, variableLowerBounds, variableUpperBounds)==ERROR){
		return ERROR;
	}
	return OK;
}

/*
 * Populates the variables upper and lower bounds
 */
int populateVariableBounds(int numofVariables, double* variableLowerBound, double* variableUpperBound)
{
	int i;
	for (i=0;i<numofVariables;i++){
		variableLowerBound[i]=0;
		variableUpperBound[i]=1;
	}
	return OK;
}

/*
 * Populates the variable Ctype definitions (Binary/Integer)
 */
int populateTypeDefinitionSettings(int numofVariables, int* variableIndices, char* variableCtypes)
{
	int i;
	for (i=0;i<numofVariables;i++){
		variableIndices[i]=i;
		variableCtypes[i]='B';
	}
	return OK;
}

/*
 * Get the variable index in CPLEX according to the the vertices it comprises.
 */
int getVariableIndexFromVertices(graph* gr,int i,int j)
{
    if(i>=j)
        return -1;
    return i*(gr-> numofVertices -1 ) - OVER_2(i) + j - i - 1;
}
